package com.google.code.jdbclogger.statement;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import com.google.code.jdbclogger.logging.Logger;
import com.google.code.jdbclogger.util.TimeCounter;

/**
 * @author Andrey Vityuk
 */
public final class StatementFactory {
    private static Constructor<Statement> statementConstructor;
    static {
	@SuppressWarnings("unchecked")
	Class<Statement> statementClass = (Class<Statement>) Proxy.getProxyClass(
		StatementFactory.class.getClassLoader(), new Class[] { Statement.class });
	try {
	    statementConstructor = statementClass.getConstructor(InvocationHandler.class);
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    public static Statement create(Statement statement, Logger logger) {
	try {
	    return statementConstructor.newInstance(new StatementInvocationHandler(statement, logger));
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    private StatementFactory() {
    }

    private static class StatementInvocationHandler implements InvocationHandler {
	private static final Set<String> EXECUTE_METHODS = new HashSet<String>(Arrays.asList("execute", "executeQuery",
		"executeUpdate"));

	private final Statement statement;
	private final Logger logger;

	public StatementInvocationHandler(Statement statement, Logger logger) {
	    this.statement = statement;
	    this.logger = logger;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    if (hasArgs(args) && isExecuteMethod(method)) {
		return invokeExecute(method, args);
	    } else {
		return invokeNotExecute(method, args);
	    }
	}

	private Object invokeExecute(Method method, Object[] args) throws Throwable {
	    String sql = (String) args[0];

	    logger.log(sql + " | ...");

	    TimeCounter counter = new TimeCounter().begin();
	    try {
		Object result = method.invoke(statement, args);

		logger.log(sql + " | " + counter.end());

		return result;
	    } catch (InvocationTargetException e) {
		Throwable underlineException = e.getCause();
		logger.log(sql + " | " + counter.end() + " --> " + underlineException.getMessage());

		throw underlineException;
	    }
	}

	private Object invokeNotExecute(Method method, Object[] args) throws Throwable {
	    try {
		return method.invoke(statement, args);
	    } catch (InvocationTargetException e) {
		Throwable underlineException = e.getCause();
		throw underlineException;
	    }
	}

	private static boolean hasArgs(Object[] args) {
	    return args != null;
	}

	private boolean isExecuteMethod(Method method) {
	    return EXECUTE_METHODS.contains(method.getName());
	}
    }
}
